﻿using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization; // DateTime.TryParseExactに使用
using System.IO;
using System.Linq;
using System.Management; //VSで参照追加必要
using System.Runtime.InteropServices;
using System.Text;

/*
ObservePC バージョン履歴

2025.10.16 Ver1.0
- Windows 10/11 + .NET Framework4.7.2 対応のコンソールツール
- WMIを用いてPC環境情報を取得
- AIとの対話時に前提整形として活用
- GUI不要、コピーペースト最適化
- 改変・配布自由（不正改変は禁止）

2025.10.17 Ver1.1
- user_note.txt の連結機能を追加
- 起動オプションによる属人属性の明示（-L, -H, -M）
- モニター情報取得機能を追加

2025.10.21 Ver1.2
- 以下の機能を追加：
  ・スタートアップ一覧
  ・Windows Update履歴
  ・NIC構成
  ・常駐プロセス一覧

2025.10.22 Ver1.3
- 以下の修正
常駐プロセス一覧がアルファベット順に出てるだけ。これでは意味をなさない
メソッドを書き換えて、サードパーティ製プロセス一覧（常駐候補）へ切り替え

2025.10.24 Ver1.4
- 以下の修正
ターゲットフレームワーク4.7.2から4.8へ変更
取得情報の強化。BIOS情報、NIC情報拡張、OSインストール日、セキュリティ状況(セキュリティソフトの推測)、タスクスケジューラ登録
管理・教材用途を考慮してLAN出力（SMB/HTTP/Syslog/SMTP）のコメントアウト＋テンプレート追加
*/



// Win32 API 関数と構造体の定義
class Program
{
    // ディスプレイ情報を取得するためのWin32 API構造体（DISPLAY_DEVICE）
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct DISPLAY_DEVICE
    {
        public int cb; // 構造体のサイズ (Marshal.SizeOf(DISPLAY_DEVICE)で初期化)
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string DeviceName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceString; // デバイスのユーザーフレンドリーな名前
        public int StateFlags;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceID;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
        public string DeviceKey;
    }

    // user32.dllからEnumDisplayDevices関数をインポート
    // これにより、Win32 APIのネイティブな機能（GPU/モニター情報取得）をC#から呼び出せる
    [DllImport("user32.dll", CharSet = CharSet.Ansi)]
    public static extern bool EnumDisplayDevices(
        string lpDevice,
        uint iDevNum,
        ref DISPLAY_DEVICE lpDisplayDevice,
        uint dwFlags
    );

    // インストール済みアプリ情報を格納するシンプルなクラス
    class InstalledApp
    {
        public string Name { get; set; }
        public DateTime? InstallDate { get; set; } // インストール日 (取得できない場合もあるためNullable)
    }

    /// <summary>
    /// プログラムのエントリポイント（メイン処理）
    /// </summary>
    static void Main(string[] args)
    {
        string ver = "ObservePC Ver1.4";
        Console.WriteLine(ver);

        // StringBuilderは、文字列を効率よく結合するためのクラス
        var sb = new StringBuilder();

        // 1. 基本情報 (時間、PC名) の取得
        OutputUptimeInfo(sb);
        sb.AppendLine();
        OutputMachineInfo(sb);
        sb.AppendLine();

        // 共通ヘッダー
        sb.AppendLine("--- [PC環境リスト] ---");

        // 2. WMIを使用したPC構成情報の取得と追記
        OutputOSInfo(sb);
        sb.AppendLine();
        OutputMotherboardInfo(sb);
        OutputBIOSInfo(sb);
        sb.AppendLine();
        OutputSystemInfo(sb);
        sb.AppendLine();
        OutputDiskInfo(sb);
        sb.AppendLine();
        OutputDeviceInfo(sb);
        sb.AppendLine();
        OutputGPUInfo(sb);
        sb.AppendLine();

        // 3. Win32 APIを使用したモニター情報の取得
        OutputDisplayInfo(sb);
        sb.AppendLine();

        //4.NIC情報の取得
        OutputNetworkInfo(sb);
        sb.AppendLine();

        // 5. レジストリを使用したの取得
        OutputSoftwareInfo(sb);
        sb.AppendLine();

        // 6. イベントログからのエラー/警告情報の取得
        OutputEventLog(sb);
        sb.AppendLine();

        //7.Windows Update履歴情報の取得
        OutputUpdateHistory(sb);
        sb.AppendLine();

        //8.スタートアップ登録情報の取得
        OutputStartupApps(sb);
        sb.AppendLine();

        //9.サードパーティ製プロセス一覧（常駐候補）
        OutputResidentProcesses(sb);
        sb.AppendLine();

        //10.タスクスケジューラ登録一覧
        OutputScheduledTasks(sb);
        sb.AppendLine();

        //11.セキュリティソフトの常駐状況
        OutputSecurityStatus(sb);
        sb.AppendLine();

        // 12. ユーザー補足情報ファイル (user_note.txt) の読み込み
        string noteFile = "user_note.txt";
        if (System.IO.File.Exists(noteFile))
        {
            sb.AppendLine("== 使用者補足情報（user_note.txt より） ==");
            // UTF8で読み込み (日本語文字化け対策)
            string[] notes = System.IO.File.ReadAllLines(noteFile, Encoding.UTF8);
            foreach (string line in notes)
            {
                sb.AppendLine(line);
            }
        }

        // 13. 起動オプションによる属人属性（AI応答プロトコルヘッダー）の追加
        List<string> userNotes = new List<string>();

        foreach (string arg in args)
        {
            // -L: Learner (初心者)
            if (arg.Equals("-L", StringComparison.OrdinalIgnoreCase))
            {
                userNotes.Add("--- [AI応答プロトコルヘッダー] ---");
                userNotes.Add("説明要求レベル: 初心者 (-L)");
                userNotes.Add("AIへの説明要求: 初心者です。専門用語を避け、平易に説明してください。");
            }
            // -H: Highly experienced (経験者)
            else if (arg.Equals("-H", StringComparison.OrdinalIgnoreCase))
            {
                userNotes.Add("--- [AI応答プロトコルヘッダー] ---");
                userNotes.Add("説明要求レベル: 経験者 (-H)");
                userNotes.Add("AIへの説明要求: 経験者です。簡潔に、要点だけを伝えてください。");
            }
            // -M=message: カスタムメッセージ
            else if (arg.StartsWith("-M=", StringComparison.OrdinalIgnoreCase) && arg.Length > 3)
            {
                string rawMessage = arg.Substring(3);
                // 引用符(' " ')やスペースをトリムしてメッセージを整形
                string message = rawMessage.Trim('"', '\'', ' ');

                if (!string.IsNullOrEmpty(message))
                {
                    userNotes.Add("--- [AI応答プロトコルヘッダー] ---");
                    userNotes.Add("説明要求レベル: 標準 (-M) + カスタム");
                    userNotes.Add($"AIへの説明要求: {message}");
                }
            }
        }

        if (userNotes.Count > 0)
        {
            sb.AppendLine();
            sb.AppendLine("== 起動オプションによる使用者属性 ==");
            foreach (string line in userNotes)
            {
                sb.AppendLine(line);
            }
        }

        // 14. AIへの相談開始を明示する行
        sb.AppendLine("以上の環境で相談があります");

        // 15. 結果をコンソールとファイルに出力 ネット出力はコメントアウト
        Console.Write(sb.ToString());
        string date = DateTime.Now.ToString("yyyyMMdd");
        string fileName = $"PC_Observation_{date}.txt";
        // ファイルに書き出し (UTF8で保存)
        System.IO.File.WriteAllText(fileName, sb.ToString(), Encoding.UTF8);

        // === 出力転送テンプレート（OutputSender.cs） ===
        // 必要に応じて、以下の関数を有効化・改変してください

        // SMB共有フォルダへコピー
        // OutputSender.SendToSharedFolder(fileName);

        // HTTP POST送信
        // OutputSender.SendToHttpServer(fileName);

        // FTPアップロード
        // OutputSender.SendToFtpServer(fileName);

        // Syslog送信（UDP 514）
        // OutputSender.SendToSyslog(fileName);

        // SMTP送信（LAN内・ポート25・認証なし）
        // OutputSender.SendViaSmtp_LAN(fileName);

        // 終了待機 LAN出力転送するならコメントアウト推奨
        Console.WriteLine("完了しました。何かキーを押すと終了します。");
        Console.ReadKey();
    }

    // --- 各情報取得メソッド ---

    /// <summary>
    /// Win32 API (EnumDisplayDevices) を使用してGPUと接続モニター情報を取得
    /// </summary>
    static void OutputDisplayInfo(StringBuilder sb)
    {
        sb.AppendLine("?? ディスプレイ情報（EnumDisplayDevicesより）：");

        // DISPLAY_DEVICE構造体を初期化し、サイズを設定
        DISPLAY_DEVICE gpu = new DISPLAY_DEVICE { cb = Marshal.SizeOf<DISPLAY_DEVICE>() };

        // 接続されているグラフィックスデバイス (GPU) を列挙
        for (uint i = 0; EnumDisplayDevices(null, i, ref gpu, 0); i++)
        {
            sb.AppendLine($"GPU {i}: {gpu.DeviceString}");

            // GPUに接続されているモニターを列挙
            DISPLAY_DEVICE monitor = new DISPLAY_DEVICE { cb = Marshal.SizeOf<DISPLAY_DEVICE>() };

            // EnumDisplayDevicesの第1引数にGPUのDeviceNameを指定することで、そのGPUに接続されたモニターを取得
            if (EnumDisplayDevices(gpu.DeviceName, 0, ref monitor, 0))
            {
                // モニター情報は通常、最初のデバイス (iDevNum=0) のみ取得できれば十分
                sb.AppendLine($"  -> モニター: {monitor.DeviceString}");
                sb.AppendLine($"     PnP名: {monitor.DeviceName}");

                // 複数モニターに対応する場合は、breakを削除し、内部ループを回す
                break;
            }

            // 次のループのために構造体を再初期化
            gpu = new DISPLAY_DEVICE { cb = Marshal.SizeOf<DISPLAY_DEVICE>() };
        }
    }


    /// <summary>
    /// WMI (Win32_OperatingSystem) からOS情報を取得
    /// </summary>
    static void OutputOSInfo(StringBuilder sb)
    {
        // WMIでOS情報 (Win32_OperatingSystem) を検索
        var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_OperatingSystem");
        foreach (ManagementObject os in searcher.Get())
        {
            sb.AppendLine($"OS: {os["Caption"]}"); // OS名 (例: Microsoft Windows 10 Pro)
            sb.AppendLine($"Version: {os["Version"]}"); // バージョン番号
            sb.AppendLine($"BuildNumber: {os["BuildNumber"]}"); // ビルド番号
                                                                // OSインストール日（WMI形式 → DateTime変換）
            if (os["InstallDate"] != null)
            {
                DateTime installDate = ManagementDateTimeConverter.ToDateTime(os["InstallDate"].ToString());
                sb.AppendLine($"OSインストール日: {installDate:yyyy-MM-dd}");
            }


        }
    }

    /// <summary>
    /// WMI (Win32_Processor, Win32_ComputerSystem, Win32_PhysicalMemory) からCPU/RAM情報を取得
    /// </summary>
    static void OutputSystemInfo(StringBuilder sb)
    {
        sb.AppendLine("?? CPUとメモリー情報：");

        // CPU情報
        var cpuSearcher = new ManagementObjectSearcher("SELECT * FROM Win32_Processor");
        foreach (ManagementObject cpu in cpuSearcher.Get())
        {
            sb.AppendLine($"CPU: {cpu["Name"]}");
            sb.AppendLine($"コア数: {cpu["NumberOfCores"]}, スレッド数: {cpu["NumberOfLogicalProcessors"]}");
        }

        // RAM合計容量
        var memSearcher = new ManagementObjectSearcher("SELECT * FROM Win32_ComputerSystem");
        foreach (ManagementObject mem in memSearcher.Get())
        {
            // TotalPhysicalMemory はバイト単位なので、MBに変換
            ulong totalMemory = Convert.ToUInt64(mem["TotalPhysicalMemory"]);
            sb.AppendLine($"RAM合計: {totalMemory / (1024 * 1024)} MB");
        }

        // RAM速度と種類 (スロット別)
        var ramSearcher = new ManagementObjectSearcher("SELECT * FROM Win32_PhysicalMemory");
        foreach (ManagementObject ram in ramSearcher.Get())
        {
            // MemoryType (24=DDR3, 26=DDR4, 34=DDR5 など。WMIの規格値を使用)
            string memoryType = GetMemoryType(Convert.ToInt32(ram["MemoryType"]));
            ulong sizeMB = Convert.ToUInt64(ram["Capacity"]) / (1024 * 1024);
            sb.AppendLine($"RAM Slot: {ram["DeviceLocator"]}, Speed: {ram["Speed"]} MHz, Type: {memoryType}, Size: {sizeMB} MB");
        }
    }

    /// <summary>
    /// WMIのMemoryTypeコードをDDR世代に変換 (C# 8.0以降のswitch式を使用 以前でも.csprojに追記で可能な場合有り)
    /// </summary>
    static string GetMemoryType(int type)
    {
        return type switch
        {
            24 => "DDR3",
            26 => "DDR4",
            34 => "DDR5",
            _ => "Unknown" // その他の値はUnknownとする
        };
    }

    /// <summary>
    /// WMI (Win32_LogicalDisk) からストレージ情報を取得
    /// </summary>
    static void OutputDiskInfo(StringBuilder sb)
    {
        sb.AppendLine("?? ストレージ情報 (容量と空き容量)：");
        // DriveType 3 = Local Disk (ローカルHDD/SSD) のみを取得
        var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_LogicalDisk WHERE DriveType = 3");

        foreach (ManagementObject disk in searcher.Get())
        {
            // バイト単位の情報をGBに変換
            ulong totalBytes = Convert.ToUInt64(disk["Size"]);
            ulong freeBytes = Convert.ToUInt64(disk["FreeSpace"]);

            string totalGB = (totalBytes / (1024 * 1024 * 1024)).ToString();
            string freeGB = (freeBytes / (1024 * 1024 * 1024)).ToString();

            sb.AppendLine($"ドライブ {disk["DeviceID"]} - 合計: {totalGB} GB, 空き: {freeGB} GB, ボリューム名: {disk["VolumeName"]}");
        }
    }

    /// <summary>
    /// WMI (Win32_PnPEntity) から主要な周辺デバイスをフィルタリングして取得
    /// </summary>
    static void OutputDeviceInfo(StringBuilder sb)
    {
        sb.AppendLine("?? 接続されているデバイス一覧（フィルタリング済）：");
        // PnP (プラグアンドプレイ) エンティティの情報を取得
        var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_PnPEntity");

        foreach (ManagementObject device in searcher.Get())
        {
            string name = device["Name"]?.ToString();

            // デバイス名に特定のキーワードが含まれるもののみ出力してノイズを減らす
            if (!string.IsNullOrEmpty(name) &&
                 (name.Contains("USB") || name.Contains("Audio") || name.Contains("Camera") || name.Contains("Display") || name.Contains("Bluetooth") || name.Contains("Printer")))
            {
                sb.AppendLine($"- {name}");
            }
        }
    }

    /// <summary>
    /// WMI (Win32_VideoController) からGPU情報を取得
    /// </summary>
    static void OutputGPUInfo(StringBuilder sb)
    {
        sb.AppendLine("?? GPU情報：");
        var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_VideoController");

        foreach (ManagementObject gpu in searcher.Get())
        {
            sb.AppendLine($"名前: {gpu["Name"]}");
            sb.AppendLine($"ドライババージョン: {gpu["DriverVersion"]}");

            // AdapterRAM (VRAM) は4GB以上でWMIの型変換に問題が出ることがあるため、チェックを追加
            if (gpu["AdapterRAM"] is ulong adapterRam)
            {
                sb.AppendLine($"VRAM: {adapterRam / (1024 * 1024)} MB");
            }
            else
            {
                sb.AppendLine("VRAM: (取得不可 or 4GB超過によるWMIの制限) MB");
            }

            // 現在の解像度情報
            if (gpu["CurrentHorizontalResolution"] != null && gpu["CurrentVerticalResolution"] != null)
            {
                sb.AppendLine($"解像度: {gpu["CurrentHorizontalResolution"]} x {gpu["CurrentVerticalResolution"]}");
            }

            sb.AppendLine();
        }
    }

    /// <summary>
    /// レジストリ (HKEY_LOCAL_MACHINE) からインストール済みソフトウェア情報を取得
    /// </summary>
    static void OutputSoftwareInfo(StringBuilder sb)
    {
        var apps = new List<InstalledApp>();
        // 64bit/32bit両方のレジストリパスから情報を検索
        string[] registryPaths = new[]
        {
            @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall", // 64bitアプリケーション
            @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall" // 32bitアプリケーション (WOW64)
        };

        foreach (string path in registryPaths)
        {
            // HKEY_LOCAL_MACHINE (ローカルPC全体の設定) を開く
            using (RegistryKey key = Registry.LocalMachine.OpenSubKey(path))
            {
                if (key == null) continue;

                foreach (string subkeyName in key.GetSubKeyNames())
                {
                    using (RegistryKey subkey = key.OpenSubKey(subkeyName))
                    {
                        string name = subkey?.GetValue("DisplayName") as string;
                        string dateStr = subkey?.GetValue("InstallDate") as string; // yyyyMMdd形式で格納されていることが多い

                        if (!string.IsNullOrEmpty(name))
                        {
                            DateTime? installDate = null;
                            // 日付文字列のパースを試みる
                            if (!string.IsNullOrEmpty(dateStr) && dateStr.Length == 8)
                            {
                                if (DateTime.TryParseExact(dateStr, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime parsedDate))
                                {
                                    installDate = parsedDate;
                                }
                            }
                            // "Update" や "Hotfix" を含むノイズをフィルタリング
                            if (!name.Contains("Update") && !name.Contains("Hotfix"))
                            {
                                apps.Add(new InstalledApp { Name = name, InstallDate = installDate });
                            }
                        }
                    }
                }
            }
        }

        // インストール日で降順に並べ替え、最新の20件を抽出
        var recentApps = apps
            .Where(a => a.InstallDate.HasValue) // 日付が取得できたもののみ
            .OrderByDescending(a => a.InstallDate.Value)
            .Take(20)
            .ToList();

        sb.AppendLine("?? 最近インストールされたソフトウェア（最大20件）:");
        foreach (var app in recentApps)
        {
            sb.AppendLine($"{app.InstallDate.Value:yyyy-MM-dd} - {app.Name}");
        }
    }

    /// <summary>
    /// Windowsイベントログから最近のエラーと警告を取得し、ノイズを除去
    /// </summary>
    static void OutputEventLog(StringBuilder sb)
    {
        sb.AppendLine("?? 最近のシステムイベントログ（Error/Warningのみ、10016除外 最大20件）:");

        // "System" ログを参照
        EventLog systemLog = new EventLog("System");
        int count = 0;

        // 最新のログから過去に向かって遡る
        for (int i = systemLog.Entries.Count - 1; i >= 0 && count < 20; i--)
        {
            EventLogEntry entry = systemLog.Entries[i];

            // エラー (Error) または警告 (Warning) のみフィルタリング
            // DCOM (EventID: 10016) は頻繁に発生し通常無害なため、ノイズとして除外
            if ((entry.EntryType == EventLogEntryType.Error || entry.EntryType == EventLogEntryType.Warning)
        && entry.Source != "DCOM" && entry.EventID != 10016)
            {
                sb.AppendLine($"[{entry.TimeGenerated:yyyy-MM-dd HH:mm:ss}] {entry.EntryType} - Source: {entry.Source}, EventID: {entry.EventID}");

                // メッセージを整形し、長すぎないように150文字でカットして出力
                string message = entry.Message.Replace('\n', ' ').Replace('\r', ' ');
                sb.AppendLine($"    -> Message: {message.Substring(0, Math.Min(message.Length, 150))}...");

                count++;
            }
        }

        if (count == 0)
        {
            sb.AppendLine("-> 過去のログに重大なError/Warningは検出されませんでした。");
        }

        // EventLogオブジェクトは通常、使い終わったらDisposeすべきですが、このコンソールアプリのライフサイクルでは省略可
        systemLog.Dispose();
    }

    /// <summary>
    /// WMI (Win32_BaseBoard) からマザーボード情報を取得
    /// </summary>
    static void OutputMotherboardInfo(StringBuilder sb)
    {
        sb.AppendLine("?? マザーボード情報：");
        var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_BaseBoard");
        foreach (ManagementObject board in searcher.Get())
        {
            sb.AppendLine($"MBメーカー: {board["Manufacturer"]}");
            sb.AppendLine($"MB製品名: {board["Product"]}");
        }
    }

    /// <summary>
    /// WMI (Win32_OperatingSystem) からPCの起動時刻と稼働時間を取得
    /// </summary>
    static void OutputUptimeInfo(StringBuilder sb)
    {
        sb.AppendLine("?? 時間情報：");

        var searcher = new ManagementObjectSearcher("SELECT LastBootUpTime FROM Win32_OperatingSystem");
        foreach (ManagementObject os in searcher.Get())
        {
            string lastBootUp = os["LastBootUpTime"]?.ToString();
            if (!string.IsNullOrEmpty(lastBootUp))
            {
                // WMIの日付形式をC#のDateTimeに変換
                DateTime bootTime = ManagementDateTimeConverter.ToDateTime(lastBootUp);
                // TimeSpan uptime = DateTime.Now - bootTime; // 稼働時間は省略 (コメントアウト)

                sb.AppendLine($"PC起動時刻: {bootTime:yyyy-MM-dd HH:mm:ss}");
            }
        }
    }

    /// <summary>
    /// 環境変数からPC名を取得
    /// </summary>
    static void OutputMachineInfo(StringBuilder sb)
    {
        sb.AppendLine("?? PC識別情報：");
        sb.AppendLine($"PC名: {Environment.MachineName}");
    }

    //スタートアップ一覧取得
    static void OutputStartupApps(StringBuilder sb)
    {
        sb.AppendLine("?? スタートアップアプリ一覧：");

        string registryPath = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run";
        using (RegistryKey key = Registry.CurrentUser.OpenSubKey(registryPath))
        {
            if (key != null)
            {
                foreach (string name in key.GetValueNames())
                {
                    string value = key.GetValue(name)?.ToString();
                    sb.AppendLine($"- {name}: {value}");
                }
            }
            else
            {
                sb.AppendLine("-> スタートアップ情報が取得できませんでした。");
            }
        }
    }

    //Windows Update履歴取得
    static void OutputUpdateHistory(StringBuilder sb)
    {
        sb.AppendLine("?? Windows Update履歴（KB番号とインストール日）：");

        var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_QuickFixEngineering");
        foreach (ManagementObject update in searcher.Get())
        {
            string hotfixId = update["HotFixID"]?.ToString();
            string installedOn = update["InstalledOn"]?.ToString();
            sb.AppendLine($"- {hotfixId} (Installed: {installedOn})");
        }
    }


    //NIC構成取得
    /*
    static void OutputNetworkInfo(StringBuilder sb)
    {
        sb.AppendLine("?? ネットワーク構成（IP/DNS/ゲートウェイ）：");
	
	//System.Net.NetworkInformationへ変更か?
	//追加予定出力項目:インターフェース名,接続種別,リンク速度,動作状態,MACアドレス,DHCPサーバーのIPアドレス,NICの製造元
	//NICの製造元はWMI?
        var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled = TRUE");
        foreach (ManagementObject nic in searcher.Get())
        {
            sb.AppendLine($"NIC: {nic["Description"]}");
            string[] ip = (string[])nic["IPAddress"];
            string[] dns = (string[])nic["DNSServerSearchOrder"];
            string[] gateway = (string[])nic["DefaultIPGateway"];

            sb.AppendLine($"  IP: {string.Join(", ", ip)}");
            sb.AppendLine($"  DNS: {string.Join(", ", dns)}");
            sb.AppendLine($"  Gateway: {string.Join(", ", gateway)}");
        }
    }
    //NIC構成取得(Ver1.4から)
    static void OutputNetworkInfo(StringBuilder sb)
    {
        sb.AppendLine("?? ネットワーク構成（System.Net.NetworkInformationより）：");

        var interfaces = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces();

        foreach (var ni in interfaces)
        {
            var props = ni.GetIPProperties();
            var ipList = props.UnicastAddresses.Select(ip => ip.Address.ToString()).ToList();
            var dnsList = props.DnsAddresses.Select(dns => dns.ToString()).ToList();
            var gatewayList = props.GatewayAddresses.Select(g => g.Address.ToString()).ToList();

            sb.AppendLine($"NIC: {ni.Name}");
            sb.AppendLine($"  種別: {ni.NetworkInterfaceType}");
            sb.AppendLine($"  状態: {ni.OperationalStatus}");
            sb.AppendLine($"  MAC: {ni.GetPhysicalAddress()}");
            sb.AppendLine($"  リンク速度: {ni.Speed / 1_000_000} Mbps");
            sb.AppendLine($"  IP: {string.Join(", ", ipList)}");
            sb.AppendLine($"  DNS: {string.Join(", ", dnsList)}");
            sb.AppendLine($"  Gateway: {string.Join(", ", gatewayList)}");
        }
    }
    */
    // NIC構成取得（Ver1.4+ 製造元付き・MAC照合方式）
    static void OutputNetworkInfo(StringBuilder sb)
    {
        sb.AppendLine("?? ネットワーク構成（System.Net + WMIより）：");

        // WMIから MAC → 製造元 の辞書を作成
        var vendorMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        try
        {
            var wmiSearcher = new ManagementObjectSearcher("SELECT * FROM Win32_NetworkAdapter WHERE PhysicalAdapter = TRUE");
            foreach (ManagementObject nic in wmiSearcher.Get())
            {
                string mac = nic["MACAddress"]?.ToString();
                string manufacturer = nic["Manufacturer"]?.ToString();
                if (!string.IsNullOrEmpty(mac) && !string.IsNullOrEmpty(manufacturer))
                {
                    string normalizedMac = mac.Replace(":", "").Replace("-", "").ToUpperInvariant();
                    vendorMap[normalizedMac] = manufacturer;
                }
            }
        }
        catch
        {
            sb.AppendLine("-> NIC製造元情報の取得に失敗しました（WMIアクセス不可）");
        }

        // System.Net.NetworkInformation でNIC状態を取得
        var interfaces = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces();

        foreach (var ni in interfaces)
        {
            var props = ni.GetIPProperties();
            var ipList = props.UnicastAddresses.Select(ip => ip.Address.ToString()).ToList();
            var dnsList = props.DnsAddresses.Select(dns => dns.ToString()).ToList();
            var gatewayList = props.GatewayAddresses.Select(g => g.Address.ToString()).ToList();

            string mac = ni.GetPhysicalAddress().ToString().ToUpperInvariant();

            sb.AppendLine($"NIC: {ni.Name}");

            if (!string.IsNullOrEmpty(mac) && vendorMap.TryGetValue(mac, out string vendor))
            {
                sb.AppendLine($"  製造元: {vendor}");
            }
            else
            {
                sb.AppendLine($"  製造元: (取得不可 or WMI照合失敗)");
            }

            sb.AppendLine($"  種別: {ni.NetworkInterfaceType}");
            sb.AppendLine($"  状態: {ni.OperationalStatus}");
            sb.AppendLine($"  MAC: {mac}");
            sb.AppendLine($"  リンク速度: {ni.Speed / 1_000_000} Mbps");
            sb.AppendLine($"  IP: {string.Join(", ", ipList)}");
            sb.AppendLine($"  DNS: {string.Join(", ", dnsList)}");
            sb.AppendLine($"  Gateway: {string.Join(", ", gatewayList)}");
        }
    }



    //常駐プロセス一覧(上限50)
    /*
    static void OutputResidentProcesses(StringBuilder sb)
    {
        sb.AppendLine("?? 実行中のプロセス一覧（常駐ソフトの推定に使用）：");

        var processes = Process.GetProcesses()
            .OrderBy(p => p.ProcessName)
            .Take(50); // 情報量制限のため上位50件に制限（必要に応じて調整）

        foreach (var proc in processes)
        {
            try
            {
                string name = proc.ProcessName;
                string path = proc.MainModule?.FileName ?? "(パス取得不可)";
                sb.AppendLine($"- {name}: {path}");
            }
            catch
            {
                // アクセス拒否などで取得できないプロセスもある
                sb.AppendLine($"- {proc.ProcessName}: (情報取得不可)");
            }
        }
    }
    */
    static void OutputResidentProcesses(StringBuilder sb)
    {
        sb.AppendLine("?? サードパーティ製プロセス一覧（常駐候補）：");

        var windowsPaths = new[]
        {
           "C:\\Windows",
           "C:\\Program Files\\WindowsApps",
           "C:\\Program Files\\Microsoft Visual Studio",
           //"C:\\Users\\<USERNAME>\\source\\repos", // 自作実行ファイル
           //"C:\\Users\\<USERNAME>\\AppData\\Local\\Microsoft", // BingSvcなど
           // 必要に応じて追加
        };
        var processes = Process.GetProcesses();

        // グループ化用の辞書：フォルダパス → プロセス一覧
        var groups = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);

        foreach (var proc in processes)
        {
            try
            {
                string name = proc.ProcessName;
                string path = proc.MainModule?.FileName ?? null;

                if (string.IsNullOrEmpty(path)) continue;

                // Windows標準プロセスを除外
                if (windowsPaths.Any(p => path.StartsWith(p, StringComparison.OrdinalIgnoreCase)))
                    continue;

                string folder = Path.GetDirectoryName(path) ?? "(不明なフォルダ)";
                string entry = $"- {name}: {path}";

                if (!groups.ContainsKey(folder))
                    groups[folder] = new List<string>();

                groups[folder].Add(entry);
            }
            catch
            {
                // アクセス拒否などは無視
            }
        }

        // 出力（フォルダ単位でグループ化）
        foreach (var kv in groups.OrderBy(k => k.Key))
        {
            sb.AppendLine($"\n■ {kv.Key}：");
            foreach (var line in kv.Value)
            {
                sb.AppendLine(line);
            }
        }
    }
    //セキュリティソフトの常駐状況
    static void OutputSecurityStatus(StringBuilder sb)
    {
        sb.AppendLine("?? セキュリティソフトの常駐状況（推定）：");

        try
        {
            var searcher = new ManagementObjectSearcher(@"root\SecurityCenter2", "SELECT * FROM AntiVirusProduct");
            foreach (ManagementObject av in searcher.Get())
            {
                sb.AppendLine($"- 製品名: {av["displayName"]}");
                sb.AppendLine($"  状態: {av["productState"]}"); // 数値コード（詳細はMS仕様）
            }
        }
        catch
        {
            sb.AppendLine("-> セキュリティセンター情報が取得できませんでした。");
        }

        // サードパーティ製の推定（プロセス名から）
        var knownAV = new[] { "eset", "avast", "norton", "mcafee", "kaspersky", "trend", "bitdefender" };
        var processes = Process.GetProcesses();

        var detected = processes
            .Where(p => knownAV.Any(av => p.ProcessName.ToLower().Contains(av)))
            .Select(p => p.ProcessName)
            .Distinct()
            .ToList();

        if (detected.Count > 0)
        {
            sb.AppendLine("-> サードパーティ製セキュリティソフトの常駐候補：");
            foreach (var name in detected)
            {
                sb.AppendLine($"- {name}");
            }
        }
        else
        {
            sb.AppendLine("-> サードパーティ製セキュリティソフトは検出されませんでした。");
        }
    }
    //BIOS情報
    static void OutputBIOSInfo(StringBuilder sb)
    {
        var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_BIOS");
        foreach (ManagementObject bios in searcher.Get())
        {
            sb.AppendLine($"BIOSメーカー: {bios["Manufacturer"]}");
            sb.AppendLine($"BIOSバージョン: {bios["SMBIOSBIOSVersion"]}");
            // BIOS日付（WMI形式から yyyy-MM-dd に変換）
            if (bios["ReleaseDate"] != null)
            {
                // WMIのDateTime形式（yyyymmddhhmmss.xxxxxx+mmm）をDateTimeに変換
                DateTime releaseDate = ManagementDateTimeConverter.ToDateTime(bios["ReleaseDate"].ToString());
                sb.AppendLine($"BIOS日付: {releaseDate:yyyy-MM-dd}"); // 整形
            }
            //sb.AppendLine($"BIOS日付: {bios["ReleaseDate"]}");
        }
    }
    /*
    //タスクスケジューラ登録一覧(不正ソフトが居るかも)
    static void OutputScheduledTasks(StringBuilder sb)
    {
        sb.AppendLine("?? タスクスケジューラ登録一覧（schtasks.exeより）：");

        try
        {
            var psi = new ProcessStartInfo
            {
                FileName = "schtasks.exe",
                Arguments = "/Query /FO LIST /V",
                RedirectStandardOutput = true,
                UseShellExecute = false,
                CreateNoWindow = true,
                StandardOutputEncoding = Encoding.UTF8
            };

            using var process = Process.Start(psi);
            using var reader = process.StandardOutput;
            string output = reader.ReadToEnd();

            // タスクごとに分割（"TaskName:" を基準に）
            var tasks = output.Split(new[] { "TaskName:" }, StringSplitOptions.RemoveEmptyEntries);

            int count = 0;
            foreach (var task in tasks)
            {
                if (count >= 20) break; // 上限20件（必要に応じて調整）

                string trimmed = task.Trim();
                if (string.IsNullOrEmpty(trimmed)) continue;

                // 不正ソフトの痕跡を探すため、"Author", "Run Command", "Schedule" などを抽出
                sb.AppendLine($"■ タスク {count + 1}:");
                foreach (var line in trimmed.Split('\n'))
                {
                    if (line.Contains("Author") || line.Contains("Task To Run") || line.Contains("Schedule") || line.Contains("Status"))
                    {
                        sb.AppendLine(line.Trim());
                    }
                }

                count++;
            }

            if (count == 0)
            {
                sb.AppendLine("-> 登録されたタスクが見つかりませんでした。");
            }
        }
        catch (Exception ex)
        {
            sb.AppendLine("-> タスクスケジューラ情報の取得に失敗しました: " + ex.Message);
        }
    }
    
    */
    // タスクスケジューラ登録一覧（不正ソフトが居るかも）
    static void OutputScheduledTasks(StringBuilder sb)
    {
        sb.AppendLine("?? タスクスケジューラ登録一覧（整形済・schtasks.exeより）：");

        try
        {
            var psi = new ProcessStartInfo
            {
                FileName = "schtasks.exe",
                Arguments = "/Query /FO LIST /V",
                RedirectStandardOutput = true,
                UseShellExecute = false,
                CreateNoWindow = true,
                StandardOutputEncoding = Encoding.GetEncoding("shift_jis") // 日本語環境対応
            };

            using var process = Process.Start(psi);
            using var reader = process.StandardOutput;
            string output = reader.ReadToEnd();

            var blocks = output.Split(new[] { "\r\n\r\n", "\n\n" }, StringSplitOptions.RemoveEmptyEntries);
            int count = 0;

            foreach (var block in blocks)
            {
                if (count >= 20) break;

                var lines = block.Split(new[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
                string name = "(取得不可)", cmd = "(取得不可)", author = "(取得不可)", schedule = "(取得不可)", status = "(取得不可)";

                foreach (var line in lines)
                {
                    var trimmed = line.Trim();

                    if (trimmed.StartsWith("TaskName:") || trimmed.StartsWith("タスク名:"))
                        name = trimmed.Split(new[] { ':' }, 2)[1].Trim();
                    else if (trimmed.StartsWith("Task To Run:") || trimmed.StartsWith("実行するタスク:"))
                        cmd = trimmed.Split(new[] { ':' }, 2)[1].Trim();
                    else if (trimmed.StartsWith("Author:") || trimmed.StartsWith("作成者:"))
                        author = trimmed.Split(new[] { ':' }, 2)[1].Trim();
                    else if (trimmed.StartsWith("Schedule:") || trimmed.StartsWith("スケジュール:"))
                        schedule = trimmed.Split(new[] { ':' }, 2)[1].Trim();
                    else if (trimmed.StartsWith("Status:") || trimmed.StartsWith("状態:"))
                        status = trimmed.Split(new[] { ':' }, 2)[1].Trim();


                }

                sb.AppendLine($"■ タスク {count + 1}: {name}");
                sb.AppendLine($"  状態: {status}");
                sb.AppendLine($"  登録者: {author}");
                sb.AppendLine($"  スケジュール: {schedule}");
                sb.AppendLine($"  実行コマンド: {cmd}");
                sb.AppendLine();

                count++;
            }

            if (count == 0)
            {
                sb.AppendLine("-> 登録されたタスクが見つかりませんでした。");
            }
        }
        catch (Exception ex)
        {
            sb.AppendLine("-> タスクスケジューラ情報の取得に失敗しました: " + ex.Message);
        }
    }


    /*
    //ユーザー情報　システム管理者向け機能なのでコメントアウト
    static void OutputUserAccounts(StringBuilder sb)
    {
        sb.AppendLine("?? ユーザーアカウント一覧（摩擦記録・未使用）：");

        try
        {
            var searcher = new ManagementObjectSearcher("SELECT Name, Disabled, Status FROM Win32_UserAccount WHERE LocalAccount = TRUE");
            foreach (ManagementObject user in searcher.Get())
            {
                string name = user["Name"]?.ToString() ?? "(取得不可)";
                string status = user["Status"]?.ToString() ?? "(状態不明)";
                bool disabled = user["Disabled"] is bool d && d;

                sb.AppendLine($"■ ユーザー: {name}");
                sb.AppendLine($"  状態: {status}");
                sb.AppendLine($"  有効: {(disabled ? "無効" : "有効")}");
            }
        }
        catch (Exception ex)
        {
            sb.AppendLine("-> ユーザー情報の取得に失敗しました: " + ex.Message);
        }
    }
    */

    /*
    // 環境変数情報（システム管理者向け機能なのでコメントアウト）
    static void OutputEnvironmentVariables(StringBuilder sb)
    {
        sb.AppendLine("?? 環境変数 (Path, Tempなど - 摩擦記録・未使用)：");
        
        // システム環境変数を取得
        var variables = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Machine);

        sb.AppendLine("■ システム環境変数:");
        // Path変数は長いため、特に重要
        if (variables["Path"] is string path)
        {
            sb.AppendLine($"Path: {path}");
        }
        
        // 重要なその他の変数も必要に応じて追加
        if (variables["ComSpec"] is string comspec)
        {
            sb.AppendLine($"ComSpec: {comspec}");
        }

        // ユーザー環境変数を取得
        var userVariables = Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User);
        sb.AppendLine("■ ユーザー環境変数:");
        if (userVariables["TEMP"] is string temp)
        {
            sb.AppendLine($"TEMP: {temp}");
        }
    }
    */



    /*
    --- 設計思想：人通信プロトコル仮説 ---

    この仮説は、人間の認知や対話における摩擦を、ネットワークプロトコルの概念で整形・観察する設計思想です。

    - 人間の認知を三層モデル（L1:入力 → L2:演算 → L3:出力）として捉える
    - 対話をOSI参照モデルに見立て、L4以降の語彙・前提整合性の違いが摩擦を生む
    - RIPのように、整合性の高い相手との通信が優先され、断絶が加速する構造を説明可能

    この仮説は、人対AIの鏡像実験にも応用可能です。
    ObservePCは、AIとの対話における前提整形テンプレートとしてPC環境を提示し、
    摩擦の構造を記録・整形することで、応答精度の向上とメトリックス悪化の回避を目指します。
    */


}